package org.example.leetcode.codeExecutor.service;

import cn.hutool.extra.spring.SpringUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.example.leetcode.codeExecutor.api.CodeExecutorService;
import org.example.leetcode.codeExecutor.utils.MinIOUtils;
import org.example.leetcode.common._enum.CodeStatusEnum;
import org.example.leetcode.common._enum.IsTestOrSubmit;
import org.example.leetcode.common._enum.LanguageEnum;
import org.example.leetcode.common.domain.CodeDO;
import org.example.leetcode.common.domain.CodeResult;
import org.example.leetcode.common.domain.FileDto;
import org.example.leetcode.common.pojo.GlobalResponse;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@Slf4j
public class CodeExecuterService implements CodeExecutorService, ApplicationRunner {
    public final Map<String, CodeExecuteFlow> map=new HashMap<>();
    public static final ThreadLocal threadLocal=new ThreadLocal();
    @Resource
    MinIOUtils minIOUtils;
    @Override
    public GlobalResponse<CodeResult> execute(CodeDO codeDO) {
        try{
        threadLocal.set(codeDO.getPhone());
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("上传代码到minio");
        CodeResult codeResult = new CodeResult();
        codeResult.setIsTestOrSubmit(codeDO.getIsTestOrSubmit());
        CodeExecuteFlow bean = map.get(codeDO.getLanguage());
        //生成代码文件并上传到minio
        MinIOUtils.FileDto codeFile = bean.createCodeFile(codeDO);
        stopWatch.stop();
        stopWatch.start("编译执行代码");
        //编译代码并执行获取输出
        String output=bean.compileFileAndExecute(codeDO,codeFile,codeResult);
        if(!StringUtils.isEmpty(codeResult.getStatus())){
            return GlobalResponse.success(codeResult);
        }
        if(StringUtils.isEmpty(output)){
            codeResult.setStatus(CodeStatusEnum.runTime_error.getCode());
            codeResult.setMessage("代码无任何输出结果");
        }
        stopWatch.stop();
        stopWatch.start("结果判断");
        //判题
        if(IsTestOrSubmit.SUBMIT.getCode().equals(codeDO.getIsTestOrSubmit())){
            judge(codeResult,codeDO,output);
        }
        else{
            codeResult.setStatus(CodeStatusEnum.success.getCode());
            codeResult.setIsTestOrSubmit(IsTestOrSubmit.TEST.getCode());
            codeResult.setTestData(output);
        }
        stopWatch.stop();
        System.out.println(stopWatch.prettyPrint());
        return GlobalResponse.success(codeResult);}
        finally {
            threadLocal.remove();
        }

    }
    public void judge(CodeResult codeResult,CodeDO codeDO,String output) {
        //判题
        try{
        InputStream answer = MinIOUtils.getObject(MinIOUtils.LEETCODE, MinIOUtils.getAnswerObject(codeDO.getTitle()));
        InputStream test=MinIOUtils.getObject(MinIOUtils.LEETCODE,MinIOUtils.getTestObject(codeDO.getTitle()));
        InputStream resultStream=new ByteArrayInputStream(output.getBytes());
        BufferedReader answerRead=new BufferedReader(new InputStreamReader(answer));
        BufferedReader resultRead=new BufferedReader(new InputStreamReader(resultStream));
        BufferedReader testRead=new BufferedReader(new InputStreamReader(test));
        String a,b,c=null;
        int correct = 0,error=0;
        //todo 用户多输出也不行
        while((a=answerRead.readLine())!=null){
            c=testRead.readLine();
            if((b=resultRead.readLine())!=null){
                log.info("正确答案{}",a);
                log.info("用户输出{}",b);
                log.info("测试用例{}",c);
                if(a.equals(b)){
                    correct++;
                }
                else{
                    error++;
                    codeResult.setCorrectAnswer(StringUtils.isEmpty(codeResult.getCorrectAnswer())?a: codeResult.getCorrectAnswer());
                    codeResult.setErrorAnswer(StringUtils.isEmpty(codeResult.getErrorAnswer())?b: codeResult.getErrorAnswer());
                    codeResult.setErrorExample(StringUtils.isEmpty(codeResult.getErrorExample())?c: codeResult.getErrorExample());
                }
            }
            else{
                error++;
                codeResult.setCorrectAnswer(StringUtils.isEmpty(codeResult.getCorrectAnswer())?a: codeResult.getCorrectAnswer());
                codeResult.setErrorAnswer(StringUtils.isEmpty(codeResult.getErrorAnswer())?b: codeResult.getErrorAnswer());
                codeResult.setErrorExample(StringUtils.isEmpty(codeResult.getErrorExample())?c: codeResult.getErrorExample());
                log.error("判题有错");
            }
        }
        double correctRate=(((double)correct/(correct+error))*100);
        correctRate=new BigDecimal(correctRate).setScale(2, RoundingMode.HALF_UP).doubleValue();
        codeResult.setCorrect(correct);
        codeResult.setError(error);
        codeResult.setCorrectRate(correctRate);
        codeResult.setSum(correct+error);
        if(error!=0){
            codeResult.setStatus(CodeStatusEnum.result_error.getCode());
        }
        else{
            codeResult.setStatus(CodeStatusEnum.success.getCode());
        }
        log.info("代码结果{}",codeResult);
        codeResult.setIsTestOrSubmit(codeDO.getIsTestOrSubmit());
        }catch (Exception e){
            codeResult.setStatus(CodeStatusEnum.result_error.getCode());
        }
    }

    @SneakyThrows
    @Override
    public GlobalResponse uploadFileToMinio(MultipartFile file,String objectName) {
        MinIOUtils.uploadFile(MinIOUtils.LEETCODE,file,objectName);
        return GlobalResponse.success(MinIOUtils.endpoint+"/"+MinIOUtils.LEETCODE+objectName);
    }

    @Override
    public GlobalResponse<InputStream> downloadFile(String bucket, String objectName) {
        try {
            return GlobalResponse.success(MinIOUtils.getObject(bucket,objectName));
        } catch (Exception e) {
            return GlobalResponse.fail();
        }
    }

    @Override
    public GlobalResponse<Boolean> removeFile(List<String> objectName) {
        objectName.forEach(item-> {
            try {
                MinIOUtils.removeFile(MinIOUtils.LEETCODE,item);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
        return GlobalResponse.success(Boolean.TRUE);
    }

    @Override
    public void run(ApplicationArguments args)  {
        for (LanguageEnum value : LanguageEnum.values()) {
            CodeExecuteFlow bean = SpringUtil.getBean(value.getBeanName());
            map.put(value.getLanguage(),bean);
        }
    }
//    MinIOUtils.minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket("leetcode").config(
//                "{\n" +
//                        "\t\"Version\": \"2012-10-17\",\n" +
//                        "\t\"Statement\": [{\n" +
//                        "\t\t\"Effect\": \"Allow\",\n" +
//                        "\t\t\"Principal\": {\n" +
//                        "\t\t\t\"AWS\": [\"*\"]\n" +
//                        "\t\t},\n" +
//                        "\t\t\"Action\": [\"s3:GetBucketLocation\", \"s3:ListBucket\", \"s3:ListBucketMultipartUploads\"],\n" +
//                        "\t\t\"Resource\": [\"arn:aws:s3:::leetcode\"]\n" +
//                        "\t}, {\n" +
//                        "\t\t\"Effect\": \"Allow\",\n" +
//                        "\t\t\"Principal\": {\n" +
//                        "\t\t\t\"AWS\": [\"*\"]\n" +
//                        "\t\t},\n" +
//                        "\t\t\"Action\": [\"s3:AbortMultipartUpload\", \"s3:DeleteObject\", \"s3:GetObject\", \"s3:ListMultipartUploadParts\", \"s3:PutObject\"],\n" +
//                        "\t\t\"Resource\": [\"arn:aws:s3:::leetcode/*\"]\n" +
//                        "\t}]\n" +
//                        "}\n").build());
}
